﻿/************************************************************************

   Extended Silverlight Toolkit

   Copyright (C) 2010-2012 Xceed Software Inc.

   This program is provided to you under the terms of the Microsoft Public
   License (Ms-PL) as published at http://extendedsilverlight.codeplex.com/license

   Please purchase a commercial version of this toolkit if you are using
   it in a commercial product. The commercial versions support the project,
   and also include more features for each control, priority support,
   source code, updates, removed watermark, and a proprietary license.

   Visit http://xceed.com and follow @datagrid on Twitter.

  **********************************************************************/

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;

namespace Xceed.Silverlight.Data
{
  [DebuggerNonUserCode]
  internal class AsyncResult : AsyncResultBase
  {
    #region Constructor

    internal AsyncResult( Guid callId, AsyncCallback userCallback, object asyncState )
      : base( callId )
    {
      m_userCallback = userCallback;
      m_asyncState = asyncState;
    }

    #endregion

    #region AsyncState Property

    public override object AsyncState
    {
      get
      {
        return m_asyncState;
      }
    }

    #endregion AsyncState Property

    #region LastResultData Property

    public object LastResultData
    {
      get;
      set;
    }

    #endregion LastResultData Property

    #region AsyncWaitHandle Property

    public override WaitHandle AsyncWaitHandle
    {
      get
      {
        if( m_waitHandle == null )
        {
          m_waitHandle = new ManualResetEvent( this.IsCompleted );
        }

        return m_waitHandle;
      }
    }

    #endregion AsyncWaitHandle Property

    #region CompletedSynchronously Property

    public override bool CompletedSynchronously
    {
      get
      {
        return m_completedSynchronously.GetValueOrDefault( false );
      }
    }

    #endregion CompletedSynchronously Property

    #region HasDependentCalls

    public bool HasDependentCalls
    {
      get
      {
        return ( m_dependentCalls.Count > 0 );
      }
    }

    #endregion HasDependentCalls

    #region IsCompleted Property

    public override bool IsCompleted
    {
      get
      {
        return ( m_dependentCalls.Count == 0 )
            && ( m_completedSynchronously.HasValue );
      }
    }

    #endregion IsCompleted Property

    #region LastIAsyncResult Property

    internal IAsyncResult LastIAsyncResult
    {
      get;
      private set;
    }

    #endregion LastIAsyncResult Property

    private static IAsyncResult BeginFakeAsyncCall( AsyncCallback callback, object asyncState )
    {
      CustomAsyncResult customAsyncResult = new CustomAsyncResult( Guid.Empty, asyncState );

      // By specifying "false" to the "synchronously" parameter, the completion will be
      // appended to the ui dispatcher event queue automaticaly, making this call asynchronous 
      // See AsyncResult.AsyncCallback.
      customAsyncResult.Complete( false, true );
      callback( customAsyncResult );

      return customAsyncResult;
    }

    private void AsyncCallback( IAsyncResult asyncResult )
    {
      // AsyncCallCompleted will be called by the async caller.
      if( asyncResult.CompletedSynchronously )
        return;

      this.AsyncCallCompleted( asyncResult );
    }

    private void AsyncCallCompleted( IAsyncResult asyncResult )
    {
      Debug.Assert( m_dependentCalls.Count != 0 );
      IAsyncResult currentAsyncResult = asyncResult;

      // This while will be handing the current callback, and 
      // every next AsyncCall that are executed synchronously
      // to avoid stack overflows.
      while( currentAsyncResult != null )
      {
        m_completedSynchronously = m_completedSynchronously.GetValueOrDefault( true ) && currentAsyncResult.CompletedSynchronously;
        DependentCallResult dependentResult = null;

        if( m_dependentCalls[ 0 ].IntermediateCallback != null )
        {
          dependentResult = m_dependentCalls[ 0 ].IntermediateCallback( this, currentAsyncResult );
        }

        if( dependentResult != null && dependentResult.NextCall != null )
        {
          m_dependentCalls[ 0 ] = dependentResult.NextCall;
        }
        else
        {
          m_dependentCalls.RemoveAt( 0 );
        }

        if( m_dependentCalls.Count > 0 )
        {
          object nextAsyncState = ( dependentResult != null ) ? dependentResult.ResultData : null;
          IAsyncResult nextAsyncResult = m_dependentCalls[ 0 ].AsyncCall( AsyncCallback, nextAsyncState );
          m_completedSynchronously = m_completedSynchronously.GetValueOrDefault( true ) && nextAsyncResult.CompletedSynchronously;
          currentAsyncResult = ( nextAsyncResult.CompletedSynchronously ) ? nextAsyncResult : null;
        }
        else
        {
          this.LastResultData = ( dependentResult != null ) ? dependentResult.ResultData : null;
          this.LastIAsyncResult = currentAsyncResult;
          currentAsyncResult = null;

          if( m_waitHandle != null )
          {
            m_waitHandle.Set();
          }

          this.FireCallback();
        }
      }
    }

    private void SetResult( object resultData, bool isSync )
    {
      if( m_dependentCalls.Count != 0 )
        throw new DataInternalException();

      m_completedSynchronously = isSync;
      this.LastResultData = resultData;

      if( m_waitHandle != null )
      {
        m_waitHandle.Set();
      }

      this.FireCallback();
    }

    internal void FireCallback()
    {
      if( m_userCallback != null )
      {
        m_userCallback( this );
      }
    }

    internal void AddDependentCall(
      Func<AsyncCallback, object, IAsyncResult> asyncCall,
      Func<AsyncResult, IAsyncResult, DependentCallResult> intermediateCallback )
    {
      this.AddDependentCall( new DependentCall( asyncCall, intermediateCallback ) );
    }

    internal void AddDependentCall( DependentCall dependentCall )
    {
      if( dependentCall == null )
        throw new ArgumentNullException( "dependentCall" );

      m_dependentCalls.Add( dependentCall );
    }

    internal void Start(
      Func<AsyncCallback, object, IAsyncResult> asyncCall,
      Func<AsyncResult, IAsyncResult, DependentCallResult> intermediateCallback )
    {
      this.Start( new DependentCall( asyncCall, intermediateCallback ) );
    }

    internal void Start( DependentCall dependentCall )
    {
      if( dependentCall == null )
        throw new ArgumentNullException( "dependentCall" );

      if( m_dependentCalls.Count != 0 )
        throw new DataInternalException();

      this.AddDependentCall( dependentCall );
      this.Start();
    }

    internal void Start()
    {
      if( m_dependentCalls.Count <= 0 )
        throw new DataInternalException();

      IAsyncResult asyncResult = m_dependentCalls[ 0 ].AsyncCall( AsyncCallback, null );
      //IAsyncResult asyncResult = m_dependentCalls[ 0 ].AsyncCall( AsyncCallback, this.LastResultData );

      if( asyncResult.CompletedSynchronously )
      {
        AsyncCallCompleted( asyncResult );
      }
    }

    internal void StartAsync( object resultData )
    {
      this.SetResult( resultData, false );
    }

    internal void Start( object resultData, bool runOnUIThread )
    {
      if( runOnUIThread )
      {
        this.Start(
          AsyncResult.BeginFakeAsyncCall,
          ( parentAr, ar ) => new DependentCallResult( resultData ) ); 
      }
      else
      {
        this.StartAsync( resultData );
      }
    }

    internal void StartSync( object resultData )
    {
      this.SetResult( resultData, true );
    }


    #region Private Fields

    private AsyncCallback m_userCallback;
    private object m_asyncState; //null;
    private Nullable<bool> m_completedSynchronously;
    private ManualResetEvent m_waitHandle; //null;
    private List<DependentCall> m_dependentCalls = new List<DependentCall>();

    #endregion Private Fields
  }
}
